$http 服务的实现细节解读

@license AngularJS v1.4.8

该代码块基于源码进行了删减
该代码块仅用于学习 分享 交流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
// :9337
var APPLICATION_JSON = 'application/json';
var CONTENT_TYPE_APPLICATION_JSON = {'Content-Type': APPLICATION_JSON + ';charset=utf-8'};
var JSON_START = /^\[|^\{(?!\{)/; // 用于 isJsonLike 判断是否为 JSON 格式的数据
var JSON_ENDS = {
'[': /]$/,
'{': /}$/
};
var JSON_PROTECTION_PREFIX = /^\)\]\}',?\n/; // 处理后端返回数据中 JSON 的安全漏洞 (新版本浏览器已经修复)

// $http 的构造函数
function $HttpProvider(){
// 默认配置参数 作用于整个构造函数的生命周期
var defaults = this.defaults = {
// 处理响应和请求数据 通过数组存放处理函数
transformResponse: [defaultHttpResponseTransform],
transformRequest: [function(d) {
return isObject(d) && !isFile(d) && !isBlob(d) && !isFormData(d) ? toJson(d) : d;
}],
// 默认的请求头 分为公共的和私有的
headers: {
common: {'Accept': 'application/json, text/plain, */*'},
post: {'Content-Type': APPLICATION_JSON + ';charset=utf-8'},
put: {'Content-Type': APPLICATION_JSON + ';charset=utf-8'},
patch: {'Content-Type': APPLICATION_JSON + ';charset=utf-8'}
},
// 跨站域请求伪造 令牌
xsrfCookieName: 'XSRF-TOKEN',
xsrfHeaderName: 'X-XSRF-TOKEN',
// 序列化参数
paramSerializer: $injector.get('$httpParamSerializer')
};

// 拦截器 目前没找到相关的处理函数
var interceptorFactories = this.interceptors = [];
// 依赖注入
this.$get = ['$httpBackend', '$$cookieReader', '$cacheFactory', '$rootScope', '$q', '$injector',
function($httpBackend, $$cookieReader, $cacheFactory, $rootScope, $q, $injector) {
// 默认的请求缓存
var defaultCache = $cacheFactory('$http');

// 主方法
function $http(requestConfig){
// 请求配置 合并传过来的配置参数
var config = extend({
method: 'get',
transformRequest: defaults.transformRequest,
transformResponse: defaults.transformResponse,
paramSerializer: defaults.paramSerializer,
}, requestConfig);
// 合并请求头 这块为了清晰简化了源代码
// 在拓展的 hreder 里面支持函数处理(代码被省略了,参见源码 executeHeaderFns ) 例如:headers: {'Content-Type': function(val){return val}}
config.headers = extend({}, defaults.headers.common, defaults.headers[lowercase(config.method)], requestConfig.headers);

// 处理请求 主要处理请求参数
function serverRequest(config){
var headers = config.headers;
// 处理请求数据
var reqData = transformData(config.data, headersGetter(headers), undefined, config.transformRequest);

// 调用发送请求函数 成功/失败 之后 执行 transformResponse
return sendReq(config, reqData).then(transformResponse, transformResponse);
}

var promise = $q.when(config); // 等同于 $q.resolve(config)

// ...
// 拦截器处理被省略了
// 最终通过 promise.then 调用
// ...
// serverRequest -> promise 直接执行 serverRequest 回调
// 此时的 promise 实际变成了 serverRequest 返回的 promise 对象
promise = promise.then(serverRequest);
// 沿用之前的 promise 处理方法,现在已被弃用
promise.success = function(fn) {
promise.then(function(response) {
fn(response.data, response.status, response.headers, config);
});
return promise;
};
promise.error = function(fn) {
promise.then(null, function(response) {
fn(response.data, response.status, response.headers, config);
});
return promise;
};

// $http 最终返回一个 promise 对象 可以进行链式调用
return promise;

// 处理响应数据
function transformResponse(response) {
// make a copy since the response must be cacheable
var resp = extend({}, response);
resp.data = transformData(response.data, response.headers, response.status,
config.transformResponse);
return (isSuccess(response.status)) // 如果状态码错误直接 执行 拒绝操作
? resp
: $q.reject(resp);
}
}

$http.pendingRequests = [];
$http.defaults = defaults;

// 对外暴露 $http 方法
return $http;


function sendReq(config, reqData){
var deferred = $q.defer(), // 创建一个异步对象 deferred 伴随一个完成的 http 请求周期
promise = deferred.promise,
cache, // 是否缓存
cachedResp,
reqHeaders = config.headers,
// 处理 URL 主要用于添加查询参数
url = buildUrl(config.url, config.paramSerializer(config.params));
// 存储请求参数 请求成功之后移除 目前没发现用处
$http.pendingRequests.push(config);
promise.then(removePendingReq, removePendingReq);

// 处理缓存
if ((config.cache || defaults.cache) && config.cache !== false &&
(config.method === 'GET' || config.method === 'JSONP')) {
cache = isObject(config.cache) ? config.cache
: isObject(defaults.cache) ? defaults.cache
: defaultCache;
}
if(cache){
// ...
}

// 添加 跨站域请求伪造 令牌
var xsrfValue = urlIsSameOrigin(config.url) // 判断是否跨域
? $$cookieReader()[config.xsrfCookieName || defaults.xsrfCookieName]
: undefined;
if (xsrfValue) {
reqHeaders[(config.xsrfHeaderName || defaults.xsrfHeaderName)] = xsrfValue;
}

// 真正 ajax 处理函数 并传递 done 作为成功/失败回调
$httpBackend(config.method, url, reqData, done, reqHeaders, config.timeout,
config.withCredentials, config.responseType);

// 返回 promise 对象 进行链式调用
return promise;

// ajax 成功/失败的处理函数
function done(status, response, headersString, statusText) {
// 缓存处理
if (cache) {
if (isSuccess(status)) {
// 成功之后将缓存所有的数据
cache.put(url, [status, response, parseHeaders(headersString), statusText]);
} else {
// remove promise from the cache
// 失败的请求移除缓存里面对应的 url
cache.remove(url);
}
}

// promise 的处理函数
resolvePromise(response, status, headersString, statusText);
}

// 根据状态执行 promise 的不同的处理函数
function resolvePromise(response, status, headers, statusText) {
//status: HTTP response status code, 0, -1 (aborted by timeout / promise)
status = status >= -1 ? status : 0;

// status: 200 ~ 299 -> 成功 最后通过 deferred.resolve/reject 结束 本次 http 请求
(isSuccess(status) ? deferred.resolve : deferred.reject)({
data: response,
status: status,
headers: headersGetter(headers),
config: config,
statusText: statusText
});
}
}

// 移除 $http.pendingRequests 中的请求配置 目前还发现 pendingRequests 的实际作用 ?
function removePendingReq() {
var idx = $http.pendingRequests.indexOf(config);
if (idx !== -1) $http.pendingRequests.splice(idx, 1);
}
}];
}

// $httpBackend = $HttpBackendProvider
function $HttpBackendProvider() {
this.$get = ['$browser', '$window', '$document', '$xhrFactory', function($browser, $window, $document, $xhrFactory) {
return createHttpBackend($browser, $xhrFactory, $browser.defer, $window.angular.callbacks, $document[0]);
}];
}
function createHttpBackend($browser, createXhr, $browserDefer, callbacks, rawDocument) {
// TODO(vojta): fix the signature
return function (method, url, post, callback, headers, timeout, withCredentials, responseType) {
url = url;

// 处理 jsonp
if (lowercase(method) == 'jsonp') {
// 生成一个回调 ID 并 挂载到全局的 angular.callbacks 上
var callbackId = '_' + (callbacks.counter++).toString(36);
// 添加回调执行函数 用于服务端执行
callbacks[callbackId] = function(data) {
callbacks[callbackId].data = data;
callbacks[callbackId].called = true;
};

// jsonp 回调 JSON_CALLBACK
var jsonpDone = jsonpReq(url.replace('JSON_CALLBACK', 'angular.callbacks.' + callbackId),
callbackId, function(status, text) {
// 完成之后执行 done 回调
completeRequest(callback, status, callbacks[callbackId].data, "", text);
callbacks[callbackId] = noop;
});
} else {
// 创建一个 xhr 对象
var xhr = createXhr(method, url);

xhr.open(method, url, true);
// 添加请求头
forEach(headers, function(value, key) {
if (isDefined(value)) {
xhr.setRequestHeader(key, value);
}
});
// 监听 ajax 请求
xhr.onload = function requestLoaded() {
var statusText = xhr.statusText || '';

// responseText is the old-school way of retrieving response (supported by IE9)
// response/responseType properties were introduced in XHR Level2 spec (supported by IE10)
// 兼容处理 IE9: responseText IE10: response/responseType
var response = ('response' in xhr) ? xhr.response : xhr.responseText;

// normalize IE9 bug (http://bugs.jquery.com/ticket/1450)
// 在 IE9 中会 返回 204 会被浏览器转成 1223
var status = xhr.status === 1223 ? 204 : xhr.status;

// fix status code when it is 0 (0 status is undocumented).
// Occurs when accessing file resources or on Android 4.1 stock browser
// while retrieving files from application cache.
if (status === 0) {
status = response ? 200 : urlResolve(url).protocol == 'file' ? 404 : 0;
}

// 完成请求 回调
completeRequest(callback,
status,
response,
xhr.getAllResponseHeaders(),
statusText);
};

// 请求出错或者取消的 状态返回 -1
var requestError = function() {
// The response is always empty
// See https://xhr.spec.whatwg.org/#request-error-steps and https://fetch.spec.whatwg.org/#concept-network-error
completeRequest(callback, -1, null, null, '');
};

xhr.onerror = requestError;
xhr.onabort = requestError;
// 设置响应类型
if (responseType) {
try {
xhr.responseType = responseType;
} catch (e) {
// WebKit added support for the json responseType value on 09/03/2013
// https://bugs.webkit.org/show_bug.cgi?id=73648. Versions of Safari prior to 7 are
// known to throw when setting the value "json" as the response type. Other older
// browsers implementing the responseType
//
// The json response type can be ignored if not supported, because JSON payloads are
// parsed on the client-side regardless.
if (responseType !== 'json') {
throw e;
}
}
}

xhr.send(isUndefined(post) ? null : post);
}

// 设置 timeout 同时 timeout 支持 promise
if (timeout > 0) {
var timeoutId = $browserDefer(timeoutRequest, timeout);
} else if (isPromiseLike(timeout)) {
timeout.then(timeoutRequest);
}
// 超时处理
function timeoutRequest() {
jsonpDone && jsonpDone();
xhr && xhr.abort();
}

function completeRequest(callback, status, response, headersString, statusText) {
// cancel timeout and subsequent timeout promise resolution
if (isDefined(timeoutId)) {
$browserDefer.cancel(timeoutId);
}
jsonpDone = xhr = null;

// 执行 done 处理函数
callback(status, response, headersString, statusText);
$browser.$$completeOutstandingRequest(noop);
}
};

// 处理 jsonp 请求
// jsonp 和 ajax 请求,对外统一,包括:请求方式 和 响应数据
function jsonpReq(url, callbackId, done) {
// we can't use jQuery/jqLite here because jQuery does crazy stuff with script elements, e.g.:
// - fetches local scripts via XHR and evals them
// - adds and immediately removes script elements from the document
// 先创建一个脚本
var script = rawDocument.createElement('script'), callback = null;
script.type = "text/javascript";
script.src = url;
script.async = true;

callback = function(event) {
removeEventListenerFn(script, "load", callback);
removeEventListenerFn(script, "error", callback);
rawDocument.body.removeChild(script);
script = null;
var status = -1; // 默认状态为 -1 请求超时和错误 返回默认状态
var text = "unknown";

if (event) {
if (event.type === "load" && !callbacks[callbackId].called) {
event = { type: "error" };
}
text = event.type;
status = event.type === "error" ? 404 : 200; // 设置成功的状态 -> 200
}

if (done) {
done(status, text);
}
};
// 监听脚本的响应
addEventListenerFn(script, "load", callback);
addEventListenerFn(script, "error", callback);
rawDocument.body.appendChild(script);
return callback;
}
}